package com.autumn.word;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Supplier;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.apache.poi.xwpf.usermodel.UnderlinePatterns;
import org.apache.poi.xwpf.usermodel.XWPFParagraph;
import org.apache.poi.xwpf.usermodel.XWPFRun;
import org.apache.poi.xwpf.usermodel.XWPFTable;
import org.apache.poi.xwpf.usermodel.XWPFTableCell;
import org.apache.poi.xwpf.usermodel.XWPFTableRow;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.STMerge;

import com.autumn.evaluator.Variant;
import com.autumn.util.ExceptionUtils;
import com.autumn.util.StringUtils;
import com.autumn.util.tuple.TupleTwo;
import com.autumn.word.TableExpressionType.ExpressionType;

/**
 * Work 内容解析
 * 
 * @author 老码农 2019-04-20 13:38:05
 */
class WorkContentEvaluate {

	private final WordSession session;

	/**
	 * 
	 * @param session
	 */
	public WorkContentEvaluate(WordSession session) {
		this.session = session;
	}

	/**
	 * 解析通用 XWPFParagraph
	 * 
	 * @param para
	 */
	public void evaluateGeneralParagraph(XWPFParagraph para) {
		this.evaluateParagraph(para, this.session.getToken().getGeneralBeginMark(),
				this.session.getToken().getGeneralEndMark());
	}

	/**
	 * 解析 XWPFParagraph
	 * 
	 * @param para
	 * @param beginMark
	 * @param endMark
	 */
	private void evaluateParagraph(XWPFParagraph para, String beginMark, String endMark) {
		List<XWPFRun> runs = new ArrayList<>(para.getRuns());
		StringBuilder sb = new StringBuilder();
		int beginIndex = -1;
		int endIndex = -1;
		String paraText = "";
		String beginRunText = "";
		String endRunText = "";
		for (int i = 0; i < runs.size(); i++) {
			XWPFRun run = runs.get(i);
			String runText = run.toString();
			sb.append(runText);
			paraText = sb.toString();
			if (beginIndex == -1 && paraText.indexOf(beginMark) >= 0) {
				if (runText.indexOf(beginMark) >= 0) {
					beginIndex = i;
					beginRunText = runText;
				} else {
					throw new WordEvaluateException(
							String.format("包含的表达式  %s 格式出错，请记事本编辑公式，所在段落 %s。", paraText, para.getText()));
				}
			}
			if (endIndex == -1 && paraText.indexOf(endMark) >= 0) {
				endIndex = i;
				endRunText = runText;
			}
			boolean result = evaluateParagraphRun(para, runs, paraText, beginIndex, beginRunText, endIndex, endRunText,
					beginMark, endMark);
			if (result) {
				beginIndex = -1;
				endIndex = -1;
				sb = new StringBuilder();
				beginRunText = "";
				endRunText = "";
			}
		}
		evaluateParagraphRun(para, runs, paraText, beginIndex, beginRunText, endIndex, endRunText, beginMark, endMark);
	}

	/**
	 * 
	 * @param para
	 * @param runs
	 * @param paraText
	 * @param beginIndex
	 * @param beginRunText
	 * @param endIndex
	 * @param endRunText
	 * @param beginMark
	 * @param endMark
	 * @return
	 */
	private boolean evaluateParagraphRun(XWPFParagraph para, List<XWPFRun> runs, String paraText, int beginIndex,
			String beginRunText, int endIndex, String endRunText, String beginMark, String endMark) {
		if (endIndex >= 0 && beginIndex >= 0 && endIndex >= beginIndex) {
			int a = paraText.indexOf(beginMark);
			int b = paraText.indexOf(endMark);
			String expression = paraText.substring(a, b + 1);
			String evlExpression;
			if (expression.length() > beginMark.length() + endMark.length()) {
				evlExpression = expression.substring(beginMark.length(), expression.length() - 1);
			} else {
				evlExpression = "";
			}
			int index = beginRunText.lastIndexOf(beginMark);
			if (index >= 0) {
				beginRunText = beginRunText.substring(0, index);
			}
			index = endRunText.indexOf(endMark);
			if (index >= 0) {
				endRunText = endRunText.substring(index + endMark.length());
			}
			Variant value = this.evaluateValue(evlExpression, beginMark, endMark);
			if (beginIndex == endIndex) {
				if (value.isNull()) {
					this.copyRun(para, runs, beginIndex, beginRunText + endRunText);
				} else {
					this.copyRun(para, runs, beginIndex, beginRunText + value.toString(false) + endRunText);
				}
			} else {
				if (endIndex - beginIndex == 1) {
					if (value.isNull()) {
						this.copyRun(para, runs, beginIndex, beginRunText);
					} else {
						this.copyRun(para, runs, beginIndex, beginRunText + value.toString(false));
					}
				} else {
					this.copyRun(para, runs, beginIndex, beginRunText);
					boolean isBegin = false;
					for (int i = beginIndex + 1; i < endIndex; i++) {
						XWPFRun run = runs.get(i);
						if (!StringUtils.isNullOrBlank(run.toString())) {
							if (!isBegin) {
								if (value.isNull()) {
									this.copyRun(para, runs, i, "");
								} else {
									this.copyRun(para, runs, i, value.toString(false));
								}
								isBegin = true;
							} else {
								this.copyRun(para, runs, i, "");
							}
						}
					}
				}
				this.copyRun(para, runs, endIndex, endRunText);
			}
			return true;
		}
		return false;
	}

	/**
	 * 解析表格
	 * 
	 * @param table
	 *            表格
	 */
	public void evaluateTable(XWPFTable table) {
		TableExpressionType type = this.getTableExpressionType(table);
		if (type.getExpressionType().equals(ExpressionType.GENERAL)
				|| type.getExpressionType().equals(ExpressionType.GENERAL_DYNAMIC)) {
			for (XWPFTableCell cell : type.getGeneralCells()) {
				List<XWPFParagraph> paragraphs = cell.getParagraphs();
				for (XWPFParagraph para : paragraphs) {
					this.evaluateGeneralParagraph(para);
				}
			}
		}
		if (type.getExpressionType().equals(ExpressionType.DYNAMIC)
				|| type.getExpressionType().equals(ExpressionType.GENERAL_DYNAMIC)) {
			this.evaluateDynamicTable(table, type);
		}
	}

	/**
	 * 解析动态表格
	 * 
	 * @param table
	 *            表格
	 * @param type
	 *            类型
	 */
	private void evaluateDynamicTable(XWPFTable table, TableExpressionType type) {
		Variant array = this.session.getContext().getVariable(type.getDynamicMemberName());
		this.clearDynamicAll(type);
		if (array == null || array.isNull()) {
			return;
		}
		if (!array.isArray()) {
			throw new WordEvaluateException(
					String.format("表格表达式集合表达式中数组变量  %s 对应的值不是数组类型。", type.getDynamicMemberName()));
		}
		Variant[] arrays = (Variant[]) array.getValue();
		if (arrays.length == 0) {
			return;
		}
		Variant[] itemArray = (Variant[]) arrays[0].getValue();
		int rowCount = itemArray.length;
		if (rowCount == 0) {
			return;
		}
		for (int i = 0; i < rowCount - 1; i++) {
			XWPFTableRow newRow = table.insertNewTableRow(type.getBeginRow());
			if (newRow != null) {
				copyRowPro(type.getFunctionFow(), newRow);
			}
		}
		int rowBeginRow = type.getBeginRow();
		int rowIndex = rowBeginRow;
		List<XWPFTableRow> rows = table.getRows();
		Map<Integer, Map<Integer, TupleTwo<String, Boolean>>> rowMap = new HashMap<>();
		for (int i = 0; i < itemArray.length; i++) {
			HashMap<String, Variant> tempArray = this.rowVariants(arrays, i);
			Map<Integer, TupleTwo<String, Boolean>> colMap = rowMap.get(rowIndex);
			if (colMap == null) {
				colMap = new HashMap<>();
				rowMap.put(rowIndex, colMap);
			}
			if (tempArray.size() > 0) {
				this.session.getContext().backup(); // 备份变量
				// 切换工作变量到当前数组行
				for (String key : tempArray.keySet()) {
					this.session.getContext().setVariable(key, tempArray.get(key));
				}
				try {
					XWPFTableRow row = rows.get(rowIndex);
					List<XWPFTableCell> cells = row.getTableCells();
					for (TableDynamicCellExcpression cellExcpression : type.getDynamicCells()) {
						this.evaluateDynamicCell(rows, row, cells, cellExcpression, rowMap, colMap, rowIndex,
								rowBeginRow);
					}
				} finally {
					this.session.getContext().revert(); // 恢复变量
				}
			}
			rowIndex++;
		}
		rowMap.clear();
	}

	/**
	 * 解析动态工作单元
	 * 
	 * @param rows
	 * @param row
	 * @param cells
	 * @param cellExcpression
	 * @param rowMap
	 * @param colMap
	 * @param rowIndex
	 * @param rowBeginRow
	 */
	private void evaluateDynamicCell(List<XWPFTableRow> rows, XWPFTableRow row, List<XWPFTableCell> cells,
			TableDynamicCellExcpression cellExcpression, Map<Integer, Map<Integer, TupleTwo<String, Boolean>>> rowMap,
			Map<Integer, TupleTwo<String, Boolean>> colMap, int rowIndex, int rowBeginRow) {
		Variant value = this.evaluateValue(cellExcpression.getExcpression(),
				this.session.getToken().getCollectionBeginMark(), this.session.getToken().getCollectionEndMark());
		int colIndex = cellExcpression.getColIndex();
		XWPFTableCell cell = cells.get(colIndex);
		String cellValue;
		if (value.isNull()) {
			cellValue = "";
		} else {
			cellValue = value.toString(false);
		}
		boolean isFirstValue = true;
		if (cellExcpression.isMergeRow()) {
			if (rowIndex > rowBeginRow) {
				int previousRow = rowIndex - 1;
				TupleTwo<String, Boolean> previousValue = rowMap.get(previousRow).get(colIndex);
				if (cellValue.equals(previousValue.getItem1())) {
					if (previousValue.getItem2()) {
						XWPFTableCell previousCell = rows.get(previousRow).getCell(colIndex);
						previousCell.getCTTc().addNewTcPr().addNewVMerge().setVal(STMerge.RESTART);
					}
					cell.getCTTc().addNewTcPr().addNewVMerge().setVal(STMerge.CONTINUE);
					this.clearCellParagraph(cell);
					isFirstValue = false;
				}
			}
			colMap.put(colIndex, new TupleTwo<>(cellValue, isFirstValue));
		}
		if (isFirstValue) {
			this.setCellText(cell, cellValue);
		}
	}

	/**
	 * 
	 * @param cell
	 */
	private void clearCellParagraph(XWPFTableCell cell) {
		List<XWPFParagraph> paragraphs = cell.getParagraphs();
		for (XWPFParagraph paragraph : paragraphs) {
			List<XWPFRun> runs = paragraph.getRuns();
			int size = runs.size();
			while (size > 0) {
				paragraph.removeRun(0);
				size--;
			}			
		}
	}

	private HashMap<String, Variant> rowVariants(Variant[] arrayArray, int arrayRowIndex) {
		HashMap<String, Variant> items = new HashMap<String, Variant>();
		for (Variant array : arrayArray) {
			if (array.isArray()) {
				Variant[] itemArray = (Variant[]) array.getValue();
				if (itemArray.length > arrayRowIndex) {
					items.put(array.getName(), itemArray[arrayRowIndex]);
				}
			}
		}
		return items;
	}

	/**
	 * 复制行
	 * 
	 * @param sourceRow
	 * @param targetRow
	 */
	private void copyRowPro(XWPFTableRow sourceRow, XWPFTableRow targetRow) {
		// 复制行属性
		targetRow.getCtRow().setTrPr(sourceRow.getCtRow().getTrPr());
		List<XWPFTableCell> cellList = sourceRow.getTableCells();
		if (null == cellList) {
			return;
		}
		// 添加列、复制列以及列中段落属性
		XWPFTableCell targetCell = null;
		for (XWPFTableCell sourceCell : cellList) {
			targetCell = targetRow.addNewTableCell();
			// 列属性
			targetCell.getCTTc().setTcPr(sourceCell.getCTTc().getTcPr());
			// 段落属性
			List<XWPFParagraph> paras = sourceCell.getParagraphs();
			while (targetCell.getParagraphs().size() < paras.size()) {
				targetCell.addParagraph();
			}
			for (int i = 0; i < paras.size(); i++) {
				XWPFParagraph para = paras.get(i);
				XWPFParagraph targetPara = targetCell.getParagraphs().get(i);
				targetPara.getCTP().setPPr(para.getCTP().getPPr());
				List<XWPFRun> runs = para.getRuns();
				for (XWPFRun xwpfRun : runs) {
					XWPFRun targetRun = targetPara.createRun();
					this.copyRun(xwpfRun, () -> {
						return targetRun;
					});
				}
			}
		}
	}

	/**
	 * 清除动态所有
	 * 
	 * @param type
	 *            类型
	 */
	private void clearDynamicAll(TableExpressionType type) {
		for (TableDynamicCellExcpression cellExcpression : type.getDynamicCells()) {
			this.setCellText(cellExcpression.getCell(), "");
		}
	}

	/**
	 * 设置单元格的值
	 * 
	 * @param cell
	 * @param text
	 */
	private void setCellText(XWPFTableCell cell, String text) {
		List<XWPFParagraph> paras = cell.getParagraphs();
		boolean isSet = true;
		for (XWPFParagraph para : paras) {
			List<XWPFRun> runs = para.getRuns();
			for (int i = 0; i < runs.size(); i++) {
				if (isSet) {
					this.copyRun(para, runs, i, text);
					isSet = false;
				} else {
					this.copyRun(para, runs, i, "");
				}
			}
		}
	}

	/**
	 * 获取表的表达式类型
	 * 
	 * @param table
	 *            表
	 * @return
	 */
	private TableExpressionType getTableExpressionType(XWPFTable table) {
		List<XWPFTableRow> rows = table.getRows();
		int generalCount = 0;
		int dynamicRows = 0;
		String dynamicMemberName = null;
		XWPFTableRow functionFow = null;
		int beginRow = -1;
		List<XWPFTableCell> generalCells = new ArrayList<>();
		List<TableDynamicCellExcpression> dynamicCells = new ArrayList<>();
		for (int r = 0; r < rows.size(); r++) {
			int generalCellCount = 0;
			int dynamicCellCount = 0;
			XWPFTableRow row = rows.get(r);
			int colIndex = 0;
			for (XWPFTableCell cell : row.getTableCells()) {
				String cellText = cell.getText();
				if (!StringUtils.isNullOrBlank(cellText)) {
					Matcher matcher = this.matcher(cellText, this.session.getToken().getGeneralBeginMark(),
							this.session.getToken().getGeneralEndMark());
					if (matcher.find()) {
						generalCount++;
						generalCellCount++;
						generalCells.add(cell);
					}
					matcher = this.matcher(cellText, this.session.getToken().getCollectionBeginMark(),
							this.session.getToken().getCollectionEndMark());
					if (matcher.find()) {
						if (matcher.groupCount() > 1) {
							throw new WordEvaluateException("表格的集合表达式[" + cellText + "]的每个单元格只能允许一个表达式。");
						}
						String[] funItems = StringUtils.split("\\" + this.session.getToken().getArrayConnectMark(),
								matcher.group(1), true);
						if (funItems.length < 2) {
							throw new WordEvaluateException("表格的集合表达式[" + cellText + "]不正确，应当为 成员名称.子成员名称 。");
						}
						String name = funItems[0].trim();
						if (!StringUtils.isNullOrBlank(dynamicMemberName)
								&& !name.equalsIgnoreCase(dynamicMemberName)) {
							throw new WordEvaluateException("集合表达式[" + cellText + "]所在的表格，有多个集合[" + name + ","
									+ dynamicMemberName + "]，一个表格只支持一个集合 。");
						}
						boolean isMergeRow = false;
						int expBeginIndex = 1;
						if (funItems.length > 2 && funItems[1].equals(this.session.getToken().getArrayMergeRowMark())) {
							isMergeRow = true;
							expBeginIndex = 2;
						}
						StringBuilder expression = new StringBuilder();
						for (int i = expBeginIndex; i < funItems.length; i++) {
							expression.append(funItems[i]);
						}
						dynamicMemberName = name;
						dynamicCellCount++;
						if (functionFow != null) {
							if (beginRow != r) {
								throw new WordEvaluateException("同一表格的集合表达不能超过两行表达式。");
							}
						} else {
							functionFow = row;
							beginRow = r;
							dynamicRows++;
						}
						TableDynamicCellExcpression cellExcpression = new TableDynamicCellExcpression(cell, colIndex,
								expression.toString(), isMergeRow);
						dynamicCells.add(cellExcpression);
					}
				}
				colIndex++;
			}
			if (generalCellCount > 0 && dynamicCellCount > 0) {
				throw new WordEvaluateException("同一表格的同一行不能同时包含常规表达式和集合表达。");
			}
		}
		TableExpressionType result;
		if (generalCount > 0 && dynamicRows > 0) {
			result = TableExpressionType.createGeneralAndDynamic(functionFow, beginRow);
		} else if (dynamicRows > 0) {
			result = TableExpressionType.createDynamic(functionFow, beginRow);
		} else if (generalCount > 0) {
			result = TableExpressionType.createGeneral();
		} else {
			result = TableExpressionType.createNone();
		}
		result.setGeneralCells(generalCells);
		result.setDynamicCells(dynamicCells);
		result.setDynamicMemberName(dynamicMemberName);
		return result;
	}

	/**
	 * 复制
	 * 
	 * @param para
	 * @param runs
	 * @param runIndex
	 * @param text
	 */
	private void copyRun(XWPFParagraph para, List<XWPFRun> runs, int runIndex, String text) {
		XWPFRun run = runs.get(runIndex);
		XWPFRun targetRun = this.copyRun(run, () -> {
			para.removeRun(runIndex);
			XWPFRun newRun = para.insertNewRun(runIndex);
			return newRun;
		});
		targetRun.setText(text);
	}

	/**
	 * 复制
	 * 
	 * @param sourceRun
	 * @param targetRunSupplier
	 */
	private XWPFRun copyRun(XWPFRun sourceRun, Supplier<XWPFRun> targetRunSupplier) {
		// CTR ctr= sourceRun.getCTR();

		boolean isBold = sourceRun.isBold();
		String color = sourceRun.getColor();
		String fontFamily = sourceRun.getFontFamily();
		int fontSize = sourceRun.getFontSize();
		boolean isItalic = sourceRun.isItalic();
		UnderlinePatterns underline = sourceRun.getUnderline();

		boolean capitalized = sourceRun.isCapitalized();
		int characterSpacing = sourceRun.getCharacterSpacing();
		boolean doubleStrikethrough = sourceRun.isDoubleStrikeThrough();
		boolean embossed = sourceRun.isEmbossed();
		boolean imprinted = sourceRun.isImprinted();
		int kerning = sourceRun.getKerning();
		String lang = sourceRun.getLang();
		boolean shadow = sourceRun.isShadowed();
		boolean smallCaps = sourceRun.isSmallCaps();
		boolean strikeThrough = sourceRun.isStrikeThrough();
		int textScale = sourceRun.getTextScale();
		boolean vanish = sourceRun.isVanish();

		XWPFRun targetRun = targetRunSupplier.get();

		targetRun.setBold(isBold);
		if (color != null) {
			targetRun.setColor(color);
		}
		if (fontFamily != null) {
			targetRun.setFontFamily(fontFamily);
		}
		if (fontSize > 0) {
			targetRun.setFontSize(fontSize);
		}
		targetRun.setItalic(isItalic);
		if (underline != null) {
			targetRun.setUnderline(underline);
		}

		targetRun.setCapitalized(capitalized);
		targetRun.setCharacterSpacing(characterSpacing);
		targetRun.setDoubleStrikethrough(doubleStrikethrough);
		targetRun.setEmbossed(embossed);
		targetRun.setImprinted(imprinted);
		targetRun.setKerning(kerning);
		if (lang != null) {
			targetRun.setLang(lang);
		}
		targetRun.setShadow(shadow);
		targetRun.setSmallCaps(smallCaps);
		targetRun.setStrikeThrough(strikeThrough);
		targetRun.setTextScale(textScale);
		if (color != null) {
			targetRun.setUnderlineColor(color);
		}
		targetRun.setVanish(vanish);

		return targetRun;
	}

	/**
	 * 解析值
	 * 
	 * @param expression
	 * @param beginMark
	 * @param endMark
	 * @return
	 */
	private Variant evaluateValue(String expression, String beginMark, String endMark) {
		if (StringUtils.isNullOrBlank(expression)) {
			return Variant.DEFAULT;
		}
		try {
			return this.session.getEvaluator().parse(this.getExpression(expression.trim()), this.session.getContext());
		} catch (Exception e) {
			throw ExceptionUtils
					.throwValidationException("\"" + beginMark + expression + endMark + "\" 语法出错:" + e.getMessage());
		}
	}

	/**
	 * 处理语法
	 * 
	 * @param expression
	 * @return
	 */
	private String getExpression(String expression) {
		String value = expression;
		while (value.contains(" (")) // 处理掉语法上的解析异常
		{
			value = value.replace(" (", "(");
		}
		return value;
	}

	/**
	 * 正则匹配字符串
	 * 
	 * @param str
	 * @param beginMark
	 *            开始标记
	 * @param endMark
	 *            结束标记
	 * @return
	 */
	public Matcher matcher(String str, String beginMark, String endMark) {
		StringBuilder regex = new StringBuilder();
		this.addConvertToken(regex, beginMark);
		regex.append("([\\s\\S]*?)");
		this.addConvertToken(regex, endMark);
		Pattern pattern = Pattern.compile(regex.toString(), Pattern.CASE_INSENSITIVE);
		Matcher matcher = pattern.matcher(str);
		return matcher;
	}

	/**
	 * 
	 * @param regex
	 * @param value
	 */
	private void addConvertToken(StringBuilder regex, String value) {
		for (int i = 0; i < value.length(); i++) {
			regex.append("\\" + Character.toString(value.charAt(i)));
		}
	}
}
